/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pyintromit_embed.hh"
#include "intromit/pyembed.h"
#include "../../util/util.hh"
#include "../../util/string.hh"
#include "../python/pymetadata_global.hh"
#include "../python/pymetadata_iface.hh"

#include <dlfcn.h>

// DEFINITIONS
#define NONE_NAME       "._None"
#define EXCEPTION_NAME  "._Exception"
#define INT_COM_CHAR    '#' // char used to distinguish internal commands from python ones

#define CC BROWN"%16s"RESET" "
#define CH "\n\t\t "GREEN
#define CE RESET"\n"

// TYPE
typedef int (*IC)(int n_param, char **param, void *ctx);

// COMMAND LIST
command_struct pyintromit_embed::command_list[]=
{
    {"#ref", (IC)pyintromit_embed::ic_ref,
			CC"python_obj..."
			CH"return number of references of objects (ob_refcnt)"CE},
    {"#load", (IC)pyintromit_embed::ic_load,
			CC"elf_file..."
			CH"load ELF files into python context"CE},
    {"#reload", (IC)pyintromit_embed::ic_reload,
			CC""
			CH"reload python context"CE},
    {"#ls", (IC)pyintromit_embed::ic_ls,
			CC"<python obj>"
			CH"list contents of current or selected context"CE},
    {"#mkdir", (IC)pyintromit_embed::ic_mkdir,
			CC"context_name"
			CH"makes a new named context in current one"CE},
    {"#cd", (IC)pyintromit_embed::ic_cd,
			CC"context_name"
			CH"go to named context [use .. to go up]"CE},
    {"#pwd", (IC)pyintromit_embed::ic_pwd,
			CC""
			CH"print current context"CE},
    {"#help", readcommand::command_list_help,
			CC"<command>"
			CH"help on internal commands"CE},
    {NULL, NULL, NULL}
};

// CLASS PYINTROMIT_EMBED
pyintromit_embed::pyintromit_embed()
{
    str=NULL;
    rc=NULL;
    main=NULL;

    exit_immediately=quiet=0;
    readline=1;
    framework_filename=NULL;
    reload=0;
}

pyintromit_embed::~pyintromit_embed()
{
    if(Py_IsInitialized())
    {
	if(framework_filename)
	{
	    // save framework
	    if(PyRun_SimpleString(str->format_volatile("__intromit__.intromit_save_context(\"%s\")", framework_filename)))
		ERROR("cannot save context: %s", framework_filename);
	}
	Py_Finalize();
    }
    command_line_finish();
    if(str) {delete str; str=NULL;}
    if(framework_filename) free(framework_filename);

    // reload
    if(reload && cl_argv)
    {
	// create command line
	const char **new_argv=(const char**)calloc(cl_narg+2, sizeof(char*));
	memcpy(new_argv, cl_argv, cl_narg*sizeof(char*));
	new_argv[cl_narg]="-q";

	if(execv(new_argv[0], const_cast<char**>(new_argv))<0)
	    ERROR("cannot reload: %s", strerror(errno));
    }
}

bool pyintromit_embed::init(int narg, char **argv)
{
    // save narg, argv
    cl_narg=narg;
    cl_argv=argv;

    // parse options with getopt
    int opt, framework=1;
    char *framework_name=NULL;
    while((opt=getopt(narg, argv, "hHf:q")) != -1)
    {
	switch(opt)
	{
	case 'h':
	    print_command_line_help();
	    exit_immediately=1;
	    readline=0;
	    break;
	case 'f':
	    framework_name=optarg;
	    break;
	case 'H':
	    if(readline) readline=2;
	    break;
	case 'q':
	    quiet++;
	    break;
	}
    }

    if(!str) str=new string;

    if(!exit_immediately && !Py_IsInitialized())
    {
	Py_InitializeEx(0); // set to 1 to set C-c to kill only python
	if(!Py_IsInitialized()) ERROR_R0("Py_InitializeEx");
	if(narg) Py_SetProgramName(argv[0]);

	// init intromit python module (implemented in python backend)
	// if initialized like this, PREFIX/lib/intromit/backends/lib/intromit/backends/pyintromit.so,
	//    it's no longer required, and avoids problems with global static variable: pymdg
	initpyintromit();

	// add python modules to path
	if(PyRun_SimpleString("import sys\n"
			      "sys.path.append(\""STR(PREFIX)"/lib/intromit/backends\")\n"
			      "del sys\n"
			      "import pyintromit as __intromit__\n"
			      "import pyintromit_embed\n"
			      "del pyintromit_embed\n")) ERROR_R0("executing python init code: PREFIX=%s", STR(PREFIX));
	if(!(main=PyImport_AddModule((char*)"__main__"))) ERROR_R0("PyImport_AddModule(__main__)");

	// load context
	if(framework)
	{
	    if(framework_name)
	    {
		if(framework_filename) free(framework_filename);
		framework_filename=strdup(framework_name);
	    }
	    else
	    {
		// create default filename
		char *home;
		if((home=getenv("HOME")))
		    framework_filename=strdup(str->format_volatile("%s/.pyintromit_embed/framework.pickle", home));
		else
		    // TODO: no home?
		    framework_filename=strdup("/tmp/framework.pickle");
	    }
	    if(PyRun_SimpleString(str->format_volatile("__intromit__.intromit_load_context(\"%s\")", framework_filename)))
		ERROR("cannot load context: %s", framework_filename);
	}
    }
    return true;
}

bool pyintromit_embed::command_line_init(const char *prompt, const char *history_file)
{
    if(!readline) return true;
    if(!rc) rc=new readcommand;
    if(!rc->init(prompt, history_file) || !rc->command_list_start(command_list, this)) return false;
    if(!rc->set_completion(completion_static, this)) ERROR_R0("readcommand.set_completion");
    if(readline==2) rc->do_not_save_history();
    return true;
}

bool pyintromit_embed::command_line_refresh()
{
    // check and refresh command line
    if(exit_immediately || !rc) return false;
    char *com;
    if(!(com=rc->command())) return false;
    if(*com && !command(com)) ; // command error

    // refresh pyintromit event queue (also controls idle states)
    pymdg.event_queue_refresh(true/*control idle time*/);
    return true;
}

bool pyintromit_embed::command_line_finish()
{
    if(rc) {delete rc; rc=NULL;}
    return true;
}

bool pyintromit_embed::command(const char *command)
{
    // try internal command first
    if(is_internal_command(command)) return rc->command_list_execution(const_cast<char*>(command));

    PyObject *obj;
    if((obj=(PyObject*)extract_object(command)))
    {
	if(PyString_Check(obj) && !strcmp(EXCEPTION_NAME, PyString_AsString(obj))) ; // do nothing
	else if(obj!=Py_None) PyRun_SimpleString("print __obj__\n");
    }
    else
    {
	int frame=str->get_frame();
	PyRun_SimpleString(str->format("__intromit__.exec_ex(\"%s\")", str->escape(command)));
	str->set_frame(frame);
    }
    return true;
}

char **pyintromit_embed::completion_static(const char *text, int start, int end, char *complete_text, void *obj_)
{
    return ((pyintromit_embed*)obj_)->completion(text, start, end, complete_text);
}

char **pyintromit_embed::completion(const char *text, int start, int end, char *complete_text)
{
    // execute command
    if(PyRun_SimpleString(str->format_volatile("__obj__=__intromit__.completion(\"%s\")\n", text)))
	ERROR_R0("PyRun_SimpleString: completion");

    // get list and generate completion
    PyObject *d=PyModule_GetDict(main);
    PyObject *list=PyDict_GetItemString(d, "__obj__");
    int n_list, i;
    if(!list || !(n_list=PyList_Size(list)))
    {
	// try with readcommand completion
	if(is_internal_command(complete_text) || !start)
	    return readcommand::command_list_completion(text, start, end, complete_text, NULL);
	else
	    return NULL;
    }

    char **comp=new char*[n_list+1];
    for(i=0; i<n_list; i++) comp[i]=strdup(PyString_AsString(PyList_GetItem(list, i)));
    comp[i]=NULL;
    return comp;
}

PyObject *pyintromit_embed::extract_object(const char *name)
{
    // execute command
    int frame=str->get_frame();
    int dev=PyRun_SimpleString(str->format("__obj__=__intromit__.extract_object(\"%s\")\n", str->escape(name)));
    str->set_frame(frame);
    if(dev) ERROR_R0("PyRun_SimpleString: extract_object");

    // get the object where settled
    PyObject *d=PyModule_GetDict(main);
    PyObject *obj=PyDict_GetItemString(d, "__obj__");
    if(!obj || obj==Py_None) return NULL; else
    {
	if(PyString_Check(obj) && !strcmp(NONE_NAME, PyString_AsString(obj))) return Py_INCREF(Py_None), Py_None;
	return obj;
    }
}

bool pyintromit_embed::is_internal_command(const char *command)
{
    while(isspace(*command)) command++;
    if(*command==INT_COM_CHAR) return true; else return false;
}

// INTERNAL COMMANDS
int pyintromit_embed::ic_ref(int n_param, char **param, pyintromit_embed *ctx)
{
    if(n_param<2)
    {
	printf(RED"\tat least one object required\n"RESET);
	return -3;
    }

    for(int i=1; i<n_param; i++)
    {
	PyObject *obj;
	if((obj=ctx->extract_object(param[i])))
	    printf(WHITE"%8d"RESET, obj->ob_refcnt);
	else
	    printf(RED"%8s"RESET, "error");

	printf(" %s\n", param[i]);
    }
    return 0;
}

int pyintromit_embed::ic_load(int n_param, char **param, pyintromit_embed *ctx)
{
    if(n_param<2)
    {
	printf(RED"\tat least one ELF file required\n"RESET);
	return -3;
    }

    for(int i=1; i<n_param; i++)
    {
	printf("Loading "WHITE"%s"RESET"... ", param[i]);
	fflush(stdout);
	if(PyRun_SimpleString(ctx->str->format_volatile("__intromit__.load_ex(\"%s\")", param[i])))
	    printf(RED"ERROR"RESET"\n");
	else
	    printf(GREEN"OK"RESET"\n");
    }
    return 0;
}

int pyintromit_embed::ic_reload(int n_param, char **param, pyintromit_embed *ctx)
{
    ctx->command_line_finish();
    ctx->exit_immediately=1;
    ctx->reload=1;
    return 0;
}

int pyintromit_embed::ic_ls(int n_param, char **param, pyintromit_embed *ctx)
{
    const char *context;
    if(n_param<2) context=""; else context=param[1];

    if(PyRun_SimpleString(ctx->str->format_volatile("__intromit__.ls_ex(\"%s\")", context))) return -1;
    return 0;
}

int pyintromit_embed::ic_mkdir(int n_param, char **param, pyintromit_embed *ctx)
{
    if(n_param<2)
    {
	printf(RED"\tnew context name required\n"RESET);
	return -3;
    }

    if(PyRun_SimpleString(ctx->str->format_volatile("__intromit__.mkdir_ex(\"%s\")", param[1]))) return -1;
    return 0;
}

int pyintromit_embed::ic_cd(int n_param, char **param, pyintromit_embed *ctx)
{
    if(n_param<2) return ic_pwd(n_param, param, ctx);

    if(PyRun_SimpleString(ctx->str->format_volatile("__intromit__.cd_ex(\"%s\")", param[1]))) return -1;
    return 0;
}

int pyintromit_embed::ic_pwd(int n_param, char **param, pyintromit_embed *ctx)
{
    if(PyRun_SimpleString(ctx->str->format_volatile("__intromit__.pwd_ex()"))) return -1;
    return 0;
}

void pyintromit_embed::print_command_line_help()
{
    fprintf(stderr, "\npyintromit_embed options:\n\n");
    fprintf(stderr, "\t-h       This command line usage\n");
    fprintf(stderr, "\t-H       Do not save history\n");
    fprintf(stderr, "\t-f file  Framework file to use\n");
    fprintf(stderr, "\t-q       Quiet execution\n");
    fprintf(stderr, "\n");
}


// C WRAPPER

pyembed_id pyembed_init()
{
    return pyembed_init_args(0, NULL);
}

pyembed_id pyembed_init_args(int narg, char **arg)
{
    pyintromit_embed *emb=new pyintromit_embed;
    if(!emb->init(narg, arg))
    {
	delete emb;
	return NULL;
    }
    return emb;    
}

void pyembed_finish(pyembed_id id)
{
    delete (pyintromit_embed*)id;
}

int pyembed_get_quiet(pyembed_id id)
{
    return ((pyintromit_embed*)id)->get_quiet();
}

int pyembed_command_line_init(pyembed_id id, const char *prompt, const char *history_file)
{
    return ((pyintromit_embed*)id)->command_line_init(prompt, history_file);
}

int pyembed_command_line_refresh(pyembed_id id)
{
    return ((pyintromit_embed*)id)->command_line_refresh();
}

int pyembed_command_line_finish(pyembed_id id)
{
    return ((pyintromit_embed*)id)->command_line_finish();
}
